#ifndef __CALLBACK_SERVICE_HPP
#define __CALLBACK_SERVICE_HPP

#include "connection.hpp"
#include "event.hpp"
#include "queue.hpp"
#include <type_traits>

namespace callback {

namespace internal {

///
/// \brief Callback service base class template for managing event publishing
/// and connection callbacks
///
/// \tparam TDerived Derived class type for static polymorphism
/// \tparam TArgs Callback function parameter types
///
template <typename TDerived, typename... TArgs> class Service {
public:
  /// \brief Function pointer type alias
  using FuncPtrType = void (*)(TArgs...);

  /// \brief Argument tuple type alias
  using TupleOfArgs = std::tuple<TArgs...>;

  ///
  /// \brief Publish an event, calling all connected callback functions
  ///
  /// \param args Arguments passed to callback functions
  ///
  static constexpr auto Publish(TArgs... args) -> void {
    PublishWithouCallingConnected(args...);
    if (m_connected != nullptr) {
      m_connected->DoCall(std::make_tuple(args...));
    }
  }

  ///
  /// \brief Connect another service as a callback target
  ///
  /// \tparam T Type of the other service
  /// \param service Service instance to connect
  ///
  template <typename T> static auto Connect(Service<T, TArgs...>) {
    static internal::Connection<TDerived, fn_connected<T>> n;

    if (m_connected == nullptr) {
      m_connected = &n;
    } else {
      m_connected->Append(n);
    }
  }

  ///
  /// \brief Bidirectionally connect two services
  ///
  /// \tparam T Type of the other service
  /// \param service Service instance to bidirectionally connect
  ///
  template <typename T> static auto BiConnect(Service<T, TArgs...>) {
    Connect(T{});
    T::template Connect(TDerived{});
  }

  ///
  /// \brief Connect a set of callable objects as callback functions
  ///
  /// \tparam ...fn Callable object types
  /// \param ...fn Callable object parameters
  ///
  static auto Connect(auto... fn)
    requires std::conjunction_v<
        std::is_invocable_r<void, decltype(fn), TArgs...>...>
  {
    static internal::Connection<TDerived, fn...> n;
    if (m_connection == nullptr) {
      m_connection = &n;
    } else {
      m_connection->Append(n);
    }
  }

  // ///
  // /// \brief Connect a set of listener callback functions
  // ///
  // /// \tparam ...fn Callable object types
  // /// \param ...fn Callable object parameters
  // ///
  // static auto ConnectListners(auto... fn)
  //   requires std::conjunction_v<
  //       std::is_invocable_r<void, decltype(fn), TArgs...>...>
  // {
  //   static internal::Connection<TDerived, fn...> n;
  //   if (m_listener == nullptr) {
  //     m_listener = &n;
  //   } else {
  //     m_listener->Append(n);
  //   }
  // }

private:
  ///
  /// \brief Set the event queue
  ///
  /// \tparam queue Queue type
  /// \param queue Queue instance
  ///
  static auto SetQueue(auto queue) {
    static_assert(zevent::is_queue_v<decltype(queue)>);
    static internal::Connection<TDerived,
                                [](TArgs... args) {
                                  event::internal::Event<TDerived> e{
                                      std::make_tuple(args...)};
                                  std::decay_t<decltype(queue)>::Push(e);
                                }>
        n;
    if (m_queue == nullptr) {
      m_queue = &n;
    }
  }

  friend class event::internal::Event<TDerived>;
  template <typename, typename...> friend class Service;
  template <std::size_t Ksize, typename...> friend class zevent::Queue;

  ///
  /// \brief Publish event without calling connected services
  ///
  /// \param args Arguments passed to callback functions
  ///
  static constexpr auto PublishWithouCallingConnected(TArgs... args) -> void {
    // if (m_listener != nullptr) {
    //   m_listener->DoCall(std::make_tuple(args...));
    // }
    CallAllConnected(args...);
    if (m_queue != nullptr) {
      m_queue->DoCall(std::make_tuple(args...));
    }
  }

  ///
  /// \brief Function object for connecting services
  ///
  /// \tparam T Connected service type
  ///
  template <typename T>
  constexpr static auto fn_connected =
      [](TArgs... args) { T::PublishWithouCallingConnected(args...); };

  /// \brief Connection base class type alias
  using ConnectionBase = ::callback::internal::ConnectionBase<TDerived>;

  ///
  /// \brief Call all connected callback functions
  ///
  /// \param args Arguments passed to callback functions
  ///
  static auto CallAllConnected(TArgs... args) -> void {
    if (m_connection != nullptr) {
      m_connection->DoCall(std::make_tuple(args...));
    }
  }

  /// \brief Head pointer of normal callback connection linked list
  inline static ConnectionBase *m_connection{nullptr};

  /// \brief Head pointer of queue connection linked list
  inline static ConnectionBase *m_queue{nullptr};

  /// \brief Head pointer of listener connection linked list
  // inline static ConnectionBase *m_listener{nullptr};

  /// \brief Head pointer of service connection linked list
  inline static ConnectionBase *m_connected{nullptr};
};

} // namespace internal

} // namespace callback

///
/// \brief Define callback service type macro
///
/// \param name Service name
/// \param ... Callback function argument types
///
#define CB_SERVICE_TYPE_DEFINE(name, ...)                                      \
  struct name : public ::callback::internal::Service<name __VA_OPT__(, )       \
                                                         __VA_ARGS__> {};

#endif // !__CALLBACK_SERVICE_HPP